/*
 * Copyright 2010 Yannick Stucki (yannickstucki.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yannickstucki.android.musicqueue.data;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.TreeSet;

import com.yannickstucki.android.musicqueue.communication.IMusicPlayer;
import com.yannickstucki.android.musicqueue.communication.ISongCommander;
import com.yannickstucki.android.musicqueue.communication.ISongEnqueuer;
import com.yannickstucki.android.musicqueue.communication.OnSongCompletedListener;
import com.yannickstucki.android.musicqueue.communication.OnUiUpdateListener;
import com.yannickstucki.android.musicqueue.songpicking.ISongPicker;

/**
 * Contains all the data about the songs.
 * 
 * @author Yannick Stucki (yannickstucki@gmail.com)
 * 
 */
public class SongLibrary implements ISongEnqueuer, ISongCommander, OnSongCompletedListener {

  /**
   * A list of all the songs.
   */
  private ArrayList<Song> allSongs;

  /**
   * A tree of the starred songs.
   */
  private TreeSet<Song> starredSongs;

  /**
   * Database to retrieve - as well as update - the starred songs.
   */
  private IDatabase database;

  /**
   * The songs currently displayed.
   */
  private Song[] displayedSongs;

  /**
   * Interface to notify the ui about data changes.
   */
  private OnUiUpdateListener updateListener;

  /**
   * Interface that decides which song is going to be picked next.
   */
  private ISongPicker songPicker;

  /**
   * Interface to control the music player.
   */
  private IMusicPlayer musicPlayer;

  /**
   * The songs currently in the queue.
   */
  private LinkedList<Song> queue = new LinkedList<Song>();

  /**
   * If this flag is set and a new song is on top, it has to be played
   * immediately.
   */
  private boolean immediatePlayFlag = false;

  /**
   * The constructor needs references to the communication interfaces and the
   * actual data about the songs.
   */
  public SongLibrary(IMusicPlayer musicPlayer, ISongPicker songPicker, IDatabase database,
      ArrayList<Song> allSongs, TreeSet<Song> starredSongs) {
    this.musicPlayer = musicPlayer;
    this.songPicker = songPicker;
    this.database = database;
    this.allSongs = allSongs;
    this.starredSongs = starredSongs;
  }

  /**
   * Sets the UI update listener.
   */
  public void setOnUiUpdateListener(OnUiUpdateListener updateListener) {
    this.updateListener = updateListener;
    if (updateListener != null) {
      updateListener.setDisplayedSongs(displayedSongs);
      updateListener.setQueue(queue);
      updateListener.onQueueChanged();
    }
  }

  /**
   * Sets the displayed songs.
   */
  public void setDisplayedSongs(Song[] displayedSongs) {
    this.displayedSongs = displayedSongs;
    if (updateListener != null) {
      updateListener.setDisplayedSongs(displayedSongs);
    }
    musicPlayer.setSong(displayedSongs[0]);
  }

  /**
   * Generetes the displayed songs anew.
   * 
   * @param count
   *          the number of songs the display shows.
   */
  public void newDisplayedSongs(int count) {
    Song[] songs = new Song[count];
    for (int i = 0; i < count; i++) {
      songs[i] = songPicker.nextSong();
    }
    setDisplayedSongs(songs);
  }

  /**
   * Returns the displayed songs.
   */
  public Song[] getDisplayedSongs() {
    return displayedSongs;
  }

  /**
   * TODO: use this.
   * 
   * Once the library is not needed anymore, the database can be closed.
   */
  public void destroy() {
    database.close();
  }

  @Override
  public void enqueue(Song song) {
    queue.addLast(song);
    if (updateListener != null) {
      updateListener.onQueueChanged();
    }
  }

  @Override
  public void enqueue(Song[] songs) {
    for (Song song : songs) {
      queue.addLast(song);
    }
    if (updateListener != null) {
      updateListener.onQueueChanged();
    }
  }

  /**
   * Returns whether there is songs waiting in the queue to be added to
   * displayed songs.
   */
  public boolean isQueueEmpty() {
    return queue.isEmpty();
  }

  /**
   * Removes the top song.
   */
  public void removeTopSong() {
    int length = displayedSongs.length;
    for (int j = 0; j < length - 1; j++) {
      displayedSongs[j] = displayedSongs[j + 1];
    }
    displayedSongs[length - 1] = null;
    if (updateListener != null) {
      updateListener.onDisplayedSongsChanged();
    }
    changeSong();
    notifySongRemoved();
  }

  @Override
  public void onSongCompleted() {
    immediatePlayFlag = true;
    removeTopSong();
  }

  @Override
  public void pause() {
    musicPlayer.pause();
    if (updateListener != null) {
      updateListener.pause();
    }
  }

  @Override
  public void play() {
    musicPlayer.play();
    if (updateListener != null) {
      updateListener.play();
    }
  }

  @Override
  public void playPause() {
    if (musicPlayer.isPlaying()) {
      pause();
    } else {
      play();
    }
  }

  @Override
  public void shuffle() {
    for (int i = 1; i < displayedSongs.length; i++) {
      if (queue.isEmpty()) {
        displayedSongs[i] = songPicker.nextSong();
      } else {
        displayedSongs[i] = queue.removeFirst();
      }
    }
    updateListener.setDisplayedSongs(displayedSongs);
    updateListener.onQueueChanged();
  }

  @Override
  public void seekTo(int milliseconds) {
    musicPlayer.seekTo(milliseconds);
  }

  @Override
  public void notifyCurrentSongChanged() {
    changeSong();
  }

  @Override
  public void notifySongRemoved() {
    Song song;
    if (isQueueEmpty()) {
      song = songPicker.nextSong();
    } else {
      song = queue.removeFirst();
      if (updateListener != null) {
        updateListener.onQueueChanged();
      }
    }
    displayedSongs[displayedSongs.length - 1] = song;
    if (updateListener != null) {
      updateListener.onDisplayedSongPushed();
    }
  }

  /**
   * Changes the playing song to the top song. Playing mode will be unchanged.
   */
  private void changeSong() {
    if (musicPlayer.isPlaying() || immediatePlayFlag) {
      musicPlayer.setSongAndPlay(displayedSongs[0]);
      immediatePlayFlag = false;
    } else {
      musicPlayer.setSong(displayedSongs[0]);
    }
  }

  @Override
  public void playNow(Song song) {
    displayedSongs[0] = song;
    immediatePlayFlag = true;
    changeSong();
    if (updateListener != null) {
      updateListener.onDisplayedSongsChanged();
    }
  }
  
  /**
   * Returns all songs.
   */
  public ArrayList<Song> getAllSongs() {
    return allSongs;
  }

  /**
   * Sets all songs.
   */
  public void setAllSongs(ArrayList<Song> allSongs) {
    this.allSongs = allSongs;
  }

  /**
   * Returns all starred songs.
   */
  public TreeSet<Song> getStarredSongs() {
    return starredSongs;
  }

  /**
   * Sets all starred songs.
   */
  public void setStarredSongs(TreeSet<Song> starredSongs) {
    this.starredSongs = starredSongs;
  }
}
